﻿using System;
using System.Collections;
using System.Collections.Generic;
using Fine.Collections.Intf;
using System.Diagnostics;
using Fine.Collections.Views;
using JetBrains.Annotations;



namespace Fine.Collections.Cont
{

    /// <summary>
    /// Linked list.
    /// </summary>
    /// <typeparam name="T">item type.</typeparam>
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(LinkListDebugView<>))]
    public class LinkList<T> : WList<T>, ICollection<T>
    {
        /// <summary>
        /// Internal implementation of the linked list.
        /// </summary>
        private LinkedListImp<T> imp = new LinkedListImp<T>();



        #region PRIVATE HELPERS


        internal Chain<T> NewChain(T item)
        {
            return new Chain<T>
                       {
                           item = item,
                           hashCode = LinkedListImp<T>.equator.GetHashCode(item)
                       };
        }


        #endregion



        #region WLIST METHODS


        public void Add(T item)
        {
            imp.InsertToEnd(NewChain(item));
        }


        public T First
        {
            get
            {
                if (imp.first != null)
                    return imp.first.item;
                else
                    throw new CollectionIsEmptyException();
            }
        }


        public T Last
        {
            get
            {
                if (imp.last != null)
                    return imp.last.item;
                else
                    throw new CollectionIsEmptyException();
            }
        }


        public int Count
        {
            get { return imp.count; }
        }


        public bool IsEmpty
        {
            get { return imp.count == 0; }
        }


        public bool IsNotEmpty
        {
            get { return imp.count > 0; }
        }


        public T this[Predicate<T> predicate]
        {
            get { return Find(predicate).Item; }
        }


        [NotNull]
        public SearchResult<T> Find(Predicate<T> predicate)
        {
            var chain = imp.FindFirst(predicate);
            return chain != null ? SearchResults<T>.Found(chain.item) : SearchResults<T>.NotFound();
        }


        public T ExcludeFirst()
        {
            return imp.ExcludeFirst();
        }


        public T ExcludeLast()
        {
            return imp.ExcludeLast();
        }


        public void Clear()
        {
            imp.Clear();
        }


        public T[] ToArray()
        {
            T[] array = new T[imp.count];
            int i = 0;
            imp.ProcessAll(x => array[i++] = x);
            return array;
        }


        public Iterator TakeIterator()
        {
            return new Iterator(this);
        }


        WIterator<T> WCollection<T>.TakeIterator()
        {
            return new Iterator(this);
        }


        RIterator<T> RCollection<T>.TakeIterator()
        {
            return new Iterator(this);
        }


        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return new Iterator(this);
        }


        IEnumerator IEnumerable.GetEnumerator()
        {
            return new Iterator(this);
        }


        public bool Contains(T item)
        {
            bool found;
            imp.CheckExistence(item, out found);
            return found;
        }


        public void CopyTo(T[] array, int startIndex)
        {
            if (array == null)
                throw new ArgumentNullException("array");

            imp.ProcessAll(x => array[startIndex++] = x);
        }


        public bool Remove(T item)
        {
            var chain = imp.FindFirst(item);
            if (chain != null)
            {
                imp.Remove(chain);
                return true;
            }
            else
            {
                return false;
            }
        }


        public bool IsReadOnly
        {
            get { return false; }
        }


        #endregion



        #region ADDITIONAL METHODS

        public void InsertAtBegin(T item)
        {
            imp.InsertAtBegin(NewChain(item));
        }


        public void ProcessAll(Action<T> action)
        {
            imp.ProcessAll(action);
        }


        #endregion



        #region ITERATOR CLASS

        public class Iterator : WIterator<T>, IEnumerator<T>
        {
            internal readonly LinkList<T> list;

            internal Chain<T> current;
            internal sbyte where;



            public Iterator(LinkList<T> list)
            {
                this.list = list;
                Reset();
            }


            private Iterator(LinkList<T> list, Chain<T> current, sbyte where)
            {
                this.list = list;
                this.current = current;
                this.where = where;
            }


            public void Reset()
            {
                current = null;
                where = -1;
            }


            public bool Next()
            {
                switch(where)
                {
                    case -1:
                        if (list.IsNotEmpty)
                        {
                            current = list.imp.first;
                            where = 0;
                        }
                        else
                        {
                            where = +1;
                        }
                        break;

                    case 0:
                        current = current.next;
                        where = current != null ? (sbyte)0 : (sbyte)+1;
                        break;
                }

                return where == 0;
            }


            bool IEnumerator.MoveNext()
            {
                return Next();
            }


            public T Current
            {
                get
                {
                    checkIteratorAtCurrent();
                    return current.item;
                }
            }


            object IEnumerator.Current
            {
                get { return Current; }
            }


            public sbyte Where
            {
                get { return where; }
            }


            public T ReplaceCurrent(T newItem)
            {
                checkIteratorAtCurrent();

                T oldItem = current.item;
                current.item = newItem;
                return oldItem;
            }


            public void InsertBefore(T item)
            {
                switch(where)
                {
                    case -1:
                        throw new OutOfCollectionException("Iterator is before the begin.");
                    case 0:
                        list.imp.InsertBefore(current, list.NewChain(item));
                        break;
                    case +1:
                        list.imp.InsertToEnd(list.NewChain(item));
                        break;
                }
            }


            public void InsertAfter(T item)
            {
                switch(where)
                {
                    case -1:
                        list.imp.InsertAtBegin(list.NewChain(item));
                        break;
                    case 0:
                        list.imp.InsertAfter(current, list.NewChain(item));
                        break;
                    case +1:
                        throw new OutOfCollectionException("Iterator is behind the end.");
                }
            }


            public T Exclude()
            {
                checkIteratorAtCurrent();
                var excluding = current;
                list.imp.Exclude(excluding);
                current = excluding.next;
                where = (current != null) ? (sbyte)0 : (sbyte)+1;

                return excluding.item;
            }



            public LinkList<T> ExtractSequence(Predicate<T> predicate)
            {
                checkIteratorAtCurrent();
                int n = 0;

                var p1 = current.prev;
                var p2 = current;

                do
                {
                    Next();
                    n++;
                } while (current != null && predicate(current.item));

                var p3 = (current != null) ? current.prev : list.imp.last;
                var p4 = current;

                p2.prev = null;
                p3.next = null;

                if (p1 != null) p1.next = p4; else list.imp.first = p4;
                if (p4 != null) p4.prev = p1; else list.imp.last = p1;
                list.imp.count -= n;

                LinkList<T> result = new LinkList<T>();
                result.imp.first = p2;
                result.imp.last = p3;
                result.imp.count = n;

                return result;
            }


            private void checkIteratorAtCurrent()
            {
                if (current == null)
                    throw new OutOfCollectionException(
                        "Iterator is out of collection: " +
                        (where < 0 ? "before the begin" : "behind the end") + ".");
            }



            public WIterator<T> Clone()
            {
                return new Iterator(list, current, where);
            }


            RIterator<T> RIterator<T>.Clone()
            {
                return new Iterator(list, current, where);
            }


            public void Dispose()
            {
                current = null;
                where = +1;
            }
        }

        #endregion
    }



    #region DEBUG HELPER

    /// <summary>
    /// Debug helper.
    /// </summary>
    internal class LinkListDebugView<T>
    {
        private readonly LinkList<T> list;


        public LinkListDebugView(LinkList<T> list)
        {
            this.list = list;
        }


        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
        public Array Items
        {
            get
            {
                if (list != null && list.IsNotEmpty)
                {
                    Array array = DebugViewUtils.MakeArray<T>(1, list.Count);
                    int i = 1;
                    list.ProcessAll(x => array.SetValue(x, i++));
                    return array;
                }
                else
                {
                    return new T[0];
                }
            }
        }
    }

    #endregion
}